﻿using DotNetCommon.Extensions;
using DotNetCommon.Serialize;
using System;
using System.Collections.Concurrent;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace DotNetCommon
{
    /// <summary>
    /// json帮助类
    /// </summary>
    public static class JsonHelper
    {
        /// <summary>
        /// 格式化json
        /// </summary>
        /// <param name="srcJson">原json</param>
        /// <param name="isIntend">是否缩进</param>
        /// <param name="removeComment">是否移除注释</param>
        /// <returns></returns>
        public static string Format(string srcJson, bool isIntend = true, bool removeComment = true)
        {
            if (srcJson.IsNullOrEmptyOrWhiteSpace()) return srcJson;
            using var doc = JsonSerializer.Deserialize<JsonDocument>(srcJson, new JsonSerializerOptions
            {
                AllowTrailingCommas = true,
                ReadCommentHandling = removeComment ? JsonCommentHandling.Skip : JsonCommentHandling.Allow,
            });
            var res = JsonSerializer.Serialize(doc, new JsonSerializerOptions
            {
                WriteIndented = isIntend,
                Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            });
            return res;
        }

        /// <summary>
        /// 判断json是否是数组
        /// </summary>
        /// <param name="srcJson"></param>
        /// <returns></returns>
        public static bool IsArray(string srcJson)
        {
            if (srcJson.IsNullOrEmptyOrWhiteSpace()) return false;
            if (srcJson.StartsWith("[")) return true;
            if (srcJson.StartsWith("{")) return false;

            var index = 0;
            var len = srcJson.Length;
            var singleComment = false;
            var multiComment = false;
            var preMultiEnd = false;
            var preComment = false;
            while (index < len)
            {
                var c = srcJson[index];
                index++;
                if (singleComment)
                {
                    if (c == '\n')
                    {
                        singleComment = false;
                    }
                    continue;
                }
                if (multiComment)
                {
                    if (preMultiEnd)
                    {
                        if (c == '/')
                        {
                            multiComment = false;
                            continue;
                        }
                        else if (c == '*')
                        {
                            continue;
                        }
                        else
                        {
                            preMultiEnd = false;
                            continue;
                        }
                    }
                    else
                    {
                        if (c == '*') preMultiEnd = true;
                        continue;
                    }
                }

                //
                if (preComment)
                {
                    if (c == '*')
                    {
                        //进入多行
                        multiComment = true;
                        preComment = false;
                        continue;
                    }
                    else if (c == '/')
                    {
                        //进入单行
                        singleComment = true;
                        preComment = false;
                        continue;
                    }
                    preComment = false;
                    continue;
                }

                //啥模式也没有
                if (c == '/')
                {
                    //预进入单行或多行注释模式
                    preComment = true;
                    continue;
                }
                else if (c == '[')
                {
                    return true;
                }
                else if (c == '{')
                {
                    return false;
                }
                else
                {
                    continue;
                }
            }
            return false;
        }

        /// <summary>
        /// 获取默认的 <c>JsonSerializerOptions</c>, 配置如下：
        /// <list type="bullet">
        /// <item>输出时 中文原样输出,而不是 \uxxxx; Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping</item>
        /// <item>输出时 包含字段; IncludeFields = true</item>
        /// <item>输出时 不包含只读字段; IgnoreReadOnlyFields = true</item>
        /// <item>输出时 忽略循环引用; ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles</item>
        /// <item>读取时 允许多余的逗号; AllowTrailingCommas = true</item>
        /// <item>读取时 跳过注释; ReadCommentHandling = System.Text.Json.JsonCommentHandling.Skip</item>
        /// <item>读取时 允许字符串转数字; NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString</item>
        /// <item>读取时 大小写不敏感; PropertyNameCaseInsensitive = true</item>
        /// </list>
        /// </summary>
        public static JsonSerializerOptions GetDefaultJsonSerializerOptions(
            string dateTimeFormatString = null,
            string dateTimeOffsetFormatString = null,
            string dateOnlyOffsetFormatString = null,
            string timeOnlyOffsetFormatString = null,
            bool enum2String = false
            )
        {
            return _getDefaultJsonSerializerOptions(dateTimeFormatString, dateTimeOffsetFormatString, dateOnlyOffsetFormatString, timeOnlyOffsetFormatString, enum2String);
        }

        internal static JsonSerializerOptions _getDefaultJsonSerializerOptions(
            string dateTimeFormatString = null,
            string dateTimeOffsetFormatString = null,
            string dateOnlyOffsetFormatString = null,
            string timeOnlyOffsetFormatString = null,
            bool enum2String = false,
            bool ignoreNull = false,
            bool lowerCamelCase = false,
            bool lowerCamelCaseDictionaryKey = false,
            bool isIntend = false,
            bool number2String = false,
            bool readOnly = false
            )
        {
            if (readOnly)
            {
                var key = $"{dateTimeFormatString}:{dateTimeOffsetFormatString}:{dateOnlyOffsetFormatString}:{timeOnlyOffsetFormatString}:{enum2String}:{ignoreNull}:{lowerCamelCase}:{lowerCamelCaseDictionaryKey}:{isIntend}:{number2String}";
                return _cache.GetOrAdd(key, () =>
                {
                    return act();
                });
            }
            else
            {
                return act();
            }
            JsonSerializerOptions act()
            {
                var options = new JsonSerializerOptions();
                Configure(options,
                    dateTimeFormatString: dateTimeFormatString,
                    dateTimeOffsetFormatString: dateTimeOffsetFormatString,
                    dateOnlyOffsetFormatString: dateOnlyOffsetFormatString,
                    timeOnlyOffsetFormatString: timeOnlyOffsetFormatString,
                    ignoreNull: ignoreNull,
                    enum2String: enum2String,
                    lowerCamelCase: lowerCamelCase,
                    lowerCamelCaseDictionaryKey: lowerCamelCaseDictionaryKey,
                    isIntend: isIntend,
                    number2String: number2String);
                return options;
            }
        }

        private static ConcurrentDictionary<string, JsonSerializerOptions> _cache = new();

        /// <summary>
        /// 对已有的 <c>JsonSerializerOptions</c> 进行配置,以增强兼容性,可用在 web开发中,如:
        /// <code>
        /// services.AddControllers().AddJsonOptions(options =>JsonHelper.Configure(options.JsonSerializerOptions, dateTimeFormatString: "yyyy-MM-dd", lowerCamelCase: true));
        /// </code>
        /// </summary>
        /// <param name="options">源对象</param>
        /// <param name="dateTimeFormatString">日期时间格式(DateTime)</param>
        /// <param name="dateTimeOffsetFormatString">日期时间格式(DateTimeOffset)</param>
        /// <param name="dateOnlyOffsetFormatString">日期时间格式(DateOnly)</param>
        /// <param name="timeOnlyOffsetFormatString">日期时间格式(TimeOnly)</param>
        /// <param name="number2String">是否将数字转为字符串</param>
        /// <param name="ignoreNull">是否忽略null值的属性</param>
        /// <param name="enum2String">是否将枚举转换为字符串</param>
        /// <param name="lowerCamelCase">属性名称的首字母是否小写</param>
        /// <param name="lowerCamelCaseDictionaryKey">字典key首字母是否小写</param>
        /// <param name="isIntend">是否格式缩进</param>
        public static void Configure(JsonSerializerOptions options,
            string dateTimeFormatString = null,
            string dateTimeOffsetFormatString = null,
            string dateOnlyOffsetFormatString = null,
            string timeOnlyOffsetFormatString = null,
            bool enum2String = false,
            bool ignoreNull = false,
            bool lowerCamelCase = false,
            bool lowerCamelCaseDictionaryKey = false,
            bool isIntend = false,
            bool number2String = false)
        {
            //中文直接输出,而不是 \uxxxx 形式
            options.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
            //允许输出Field
            options.IncludeFields = true;
            //忽略只读Field
            options.IgnoreReadOnlyFields = true;
            //忽略循环引用
            options.ReferenceHandler = ReferenceHandler.IgnoreCycles;
            //允许存在多余的逗号
            options.AllowTrailingCommas = true;
            //跳过注释
            options.ReadCommentHandling = JsonCommentHandling.Skip;
            //允许将 "123" 转为数字 123
            //允许 {"Prop":"NaN"} 浮点型,但不允许 {\"Prop\":NaN},这点无法做到与Newtonsoft兼容
            options.NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.AllowNamedFloatingPointLiterals;
            //允许忽略属性名大小写
            options.PropertyNameCaseInsensitive = true;

            //解决 "" => int? 报错问题
            options.Converters.Add(new NullAbleConverter());
            //解决 1 => bool 报错问题
            options.Converters.Add(new JsonConverterBool());
            //解决 "Open" => enum 报错问题
            options.Converters.Add(new JsonEnumConverter(enum2String));
            //解决反序列化问题: 123 => "123"
            options.Converters.Add(new JsonConverterString());

            //DateOnly & TimeOnly
            if (Environment.Version.Major == 6)
            {
                if (dateOnlyOffsetFormatString.IsNotNullOrEmptyOrWhiteSpace()) options.Converters.Add(new JsonConverterDateOnly(dateOnlyOffsetFormatString));
                else options.Converters.Add(new JsonConverterDateOnly());
                if (timeOnlyOffsetFormatString.IsNotNullOrEmptyOrWhiteSpace()) options.Converters.Add(new JsonConverterTimeOnly(timeOnlyOffsetFormatString));
                else options.Converters.Add(new JsonConverterTimeOnly());
            }
            else
            {
                if (dateOnlyOffsetFormatString.IsNotNullOrEmptyOrWhiteSpace()) options.Converters.Add(new JsonConverterDateOnly(dateOnlyOffsetFormatString));
                if (dateOnlyOffsetFormatString.IsNotNullOrEmptyOrWhiteSpace()) options.Converters.Add(new JsonConverterDateOnly(dateOnlyOffsetFormatString));
                if (timeOnlyOffsetFormatString.IsNotNullOrEmptyOrWhiteSpace()) options.Converters.Add(new JsonConverterTimeOnly(timeOnlyOffsetFormatString));
            }
            //Datetime & DateTimeOffset
            if (dateTimeFormatString.IsNotNullOrEmptyOrWhiteSpace()) options.Converters.Add(new JsonConverterDatetime(dateTimeFormatString));
            else options.Converters.Add(new JsonConverterDatetime());
            if (dateTimeOffsetFormatString.IsNotNullOrEmptyOrWhiteSpace()) options.Converters.Add(new JsonConverterDateTimeOffset(dateTimeOffsetFormatString));
            else options.Converters.Add(new JsonConverterDateTimeOffset());

            if (ignoreNull) options.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
            if (lowerCamelCase) options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
            if (lowerCamelCaseDictionaryKey) options.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
            if (isIntend) options.WriteIndented = true;
            if (number2String) options.NumberHandling = options.NumberHandling | JsonNumberHandling.WriteAsString;
        }
    }
}
